package org.reborn.engine.render.texture

import org.reborn.utils.niobuffer.BufferUtil
import org.reborn.utils.FileUtil

import java.nio.{Buffer,ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,FloatBuffer,LongBuffer,DoubleBuffer}

import javax.media.opengl.{GL,GL2,GL2GL3,GL2ES2}
import javax.imageio.ImageIO
import java.awt.image.{BufferedImage,Raster}
import java.awt.Image

object TextureType extends Enumeration{
  type TextureType = Value
  
  val Tex1D,Tex2D,Tex3D,TexCubeMap = Value
}

import TextureType._

class ManagedTexture(val tex:Texture){
  private var users:Int = 0
  def increaseUserCount:ManagedTexture = { users +=1; this }
  def decreaseUserCount:ManagedTexture = { users -=1; this }
  def userCount = users
  
  def asTex2D:Texture2D = tex.asInstanceOf[Texture2D]
}

class CachingTextureManager{
  protected val TEX_FROM_BUFFER = "From Buffer: ";
  protected var bufferTextures = 0;
  protected var currentTextures:Map[(String,TextureType),ManagedTexture] = Map()
  
  var glInternalFormat = GL.GL_RGB
  var minFilter = TextureFilter.Linear
  var magFilter = TextureFilter.Linear
  var repeatS = TextureWrap.Repeat
  var repeatT = TextureWrap.Repeat
  var repeatR = TextureWrap.Repeat
  var generateMipMaps = false
  
  /**
   * Free the specified texture from video memory 
   * if this is the last reference to the texture
   * @param gl The GL object
   * @param texture the texture to delete 
   */
  def DeleteTexture(gl:GL,texture:Texture){
	currentTextures.find{keyAndTex =>
	  val (key,tex) = keyAndTex
	  tex.tex == texture
    }.foreach{keyAndTex =>
	  val (key,tex) = keyAndTex
	  
	  removeTextureFromMap(gl,key,tex)
	}
  }
  
  /**
   * Free all managed textures from video memory, even textures
   * that still have multiple references
   */
  def DeleteAllTextures(gl:GL){
	currentTextures.foreach{keyAndTex =>
	  val (key,tex) = keyAndTex
	  
	  while(tex.userCount >0)
	 	tex.decreaseUserCount
	 	
	  removeTextureFromMap(gl,key,tex)
	}
  }
  
  protected def addTextureToMap[T <: Texture]
        (gl:GL,key:(String,TextureType))
  		(newTexFunc:()=>Option[T])
  		(implicit defaultTexFunc:(GL)=>T):T = {
	
	val manTex:ManagedTexture = if(!currentTextures.contains(key)){
	  val newTexOpt = newTexFunc()
	  
	  if(newTexOpt.isEmpty){
	 	System.err.println("Could not create texture from image "+key._1+". Using default texture.")
	 	return defaultTexFunc(gl)
	  }
	  
	  val managedTexture = new ManagedTexture(newTexOpt.get)
	  newTexOpt.foreach{(newTex) => currentTextures += (key -> managedTexture)}
	  
	  println("Creating texture " + key._1)
	   
	  managedTexture
	}
	else{
	  val managedTexture = currentTextures(key) 
	  println("Increasing user count for texture "+key._1+" to "+(managedTexture.userCount+1))
	  currentTextures(key)
	}
	
	manTex.increaseUserCount
	
	manTex.tex.asInstanceOf[T]
  }
  
  protected def removeTextureFromMap(gl:GL,key:(String,TextureType),tex:ManagedTexture) = {
	tex.decreaseUserCount
	 
	if(tex.userCount <= 0){
	  tex.tex.deleteTexture(gl)
	  currentTextures -= key
 	  println("Deleting texture " + key._1) 	
	}
	else{
	  println("Decreasing user count for texture " + key._1 + " to "+tex.userCount)
	}
  }
  
  protected def updateCommonMetadata(metadata:BaseTextureMetadata){
	metadata.glInternalFormat = glInternalFormat
	metadata.minFilter = minFilter
    metadata.magFilter = magFilter
    metadata.repeatS = repeatS
    metadata.repeatT = repeatT
    metadata.repeatR = repeatR
	metadata.generateMipMaps = generateMipMaps
  }
  
  protected def getNewBufferFileName:String = {
	bufferTextures += 1
	TEX_FROM_BUFFER + bufferTextures
  }
  
  protected def getImgDataFromFile(fileName:String):Option[(Int,Int,Int,Int,Buffer)] = {
	val streamOpt = FileUtil.Load(fileName)
	streamOpt.foreach{(stream) =>
	  val imageOpt = Option(ImageIO.read(stream))
	  
	  imageOpt.foreach{(image) =>
	    getFormatTypeAndBuffer(image).foreach{(formatTypeBuffer) =>
	      val (glFormat,glType,buffer) = formatTypeBuffer
	      return Some((image.getWidth,image.getHeight,glFormat,glType,buffer))
	    }
	  }
	}
	
	return None
  }
  
  protected def getFormatTypeAndBuffer(image:BufferedImage):Option[(Int,Int,Buffer)] = {
	image.getType match{
		case BufferedImage.TYPE_3BYTE_BGR	=> Some((GL.GL_RGB,GL.GL_UNSIGNED_BYTE,getByteBuffer(image,3)))
		case BufferedImage.TYPE_4BYTE_ABGR	=> Some((GL.GL_RGBA,GL.GL_UNSIGNED_BYTE,getByteBuffer(image,4)))
		case BufferedImage.TYPE_BYTE_GRAY	=> Some((GL.GL_LUMINANCE,GL.GL_UNSIGNED_BYTE,getByteBuffer(image,1)))
		case _ => None
	}
  }
  
  protected def getByteBuffer(img:BufferedImage,components:Int):ByteBuffer = {
	val size = img.getWidth * img.getHeight
	val buffer = BufferUtil.createDirectByteBuffer(components * size)
	
	//OpenGL also needs the data with origin starting at lower left, instead of 
	//upper left, so flip vertically
	for(y <- (img.getHeight -1) until -1 by -1;
		x <- 0 until img.getWidth){
		
	  val argb = img.getRGB(x,y)
	  val rgba = Array[Int](
	 		  (argb >> 16) & 0xff,		//RED
	 		  (argb >> 8) & 0xff,		//GREEN
	 		  argb  & 0xff,				//BLUE
	 		  (argb >> 24) & 0xff)		//ALPHA
	  
	  for(component <- 0 until components){
	 	buffer.put(rgba(component).asInstanceOf[Byte])
	  }
	}
	
	return buffer
  }
  
  protected def testTextureBuffer:Buffer = {
	val image = BufferUtil.createDirectByteBuffer(4 * 4)
	for(i <- 0 until image.capacity){
	  image.put(0x80.asInstanceOf[Byte])
	}
	image.put(0 + 0, 0xFF.asInstanceOf[Byte])
	image.put(4 + 1, 0xFF.asInstanceOf[Byte])
	image.put(8 + 2, 0xFF.asInstanceOf[Byte])
	
	image.rewind
  }
}

/**
 * Manages Textures. Creation, deletion, and caching. 
 */
object TextureManager 
	extends CachingTextureManager
	with TextureManager1DSupport
	with TextureManager2DSupport
	with TextureManagerCubeMapSupport
	with TextureManager3DSupport{

}